#!/bin/bash
start_time=$(date +%s)
#=====================================================================
# JR Backup
# http://jeff.robbins.ws/code/jrbackup
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#=====================================================================
#
# __Dependencies__
# GNU Tar, Bzip2, awk, mysqldump
# Configured local MTA (for mail command, no support for remote SMTP server)
#
#=====================================================================

#Where should the email be sent when the script completes
EMAIL="user@host.com"
 
#What directory do you want to backup, absolute path (begin with a '/')
TOBACKUP="/home/jrobbins/testbackup"
 
#What is the name of this backup job
BACKUPNAME="testBackup"
 
#Where should the backups be kept, absolute path (begin with a '/')
# A subdirectory will be created in this directory with the backup name
BACKUPTO="/home/jrobbins/backups"
 
#Subdirectories of TOBACKUP that should be excluded from the backup
#directories should be separated by 1 space
#speficy directories relative to BACKUPTO directory
EXCLUDEDIRS="cache testdir1/testdir1-1 10mbFile.tmp"

#How many weeks worth of daily backups should we keep before we start rolling over
#eg. 2 == 14 days of daily backups before the oldest get over-written
#If a file only exists for 24 hours, a daily backup will get it, but a weekly or monthly will not 
#(unless it happened to be created on the same day we do a weekly but that a 1 in 7 chance)
#This variable allows us to keep more than one weeks worth of dailies, 
#increasing the window of recognition that a file is missing
#Values: 1, 2, 3, or 4
DAILYROLLWEEKS=5

#Should we do weekly backups
WEEKLY="no"

#If we are doing weekly backups, what day of the week should the backup happen
# 1 == Monday
WEEKLYDAY=7

#How many weekly backups should we keep before we start rolling over
#5 is a good number considering there is never more than 5 weeks in a month
WEEKLYROLL=5

#Should we perform monthly backups
MONTHLY="no"

#If we are doing monthly backups, what day of the month should the backup happen
#Must be two digits, single digit days must have a preceeding zero (ie. 01, 02, 03, etc.)
MONTHLYDAY=01

#Should monthly backups over 1 year old be purged when a new monthly takes place
#Probably the best idea to keep this turned on
MONTHLYPURGE="yes"

#If you want all the details of the backup [ yes|no ]
DEBUG='yes'

##### EXPERIMENTAL Database dump functionality #########

#Shall we backup some database with this backup also? [ yes|no ]
DBBACKUP='yes'

# Username to access the MySQL server e.g. dbuser
DBUSERNAME='backups'

# Username to access the MySQL server e.g. password
DBPASSWORD='b@ckup5'

# List of DBNAMES for backup, separated by spaces
DBNAMES="psa ocsweb"

#Name of directory in backup root where db dumps will be put
DBDUMPDIR="DBData"
 
######################################################### 
########### That should be it, stop editing #############
######################################################### 

#initialize some variables
BACKUPDIR="${BACKUPTO}/${BACKUPNAME}"
DATE=`date +%Y%m%d`   #Datestamp eg. 20100322
DOW=`date +%A`        #Day name of the week eg. Wednesday
DNOW=`date +%u`       #Day of the week number 1==Monday
DOM=`date +%d`        #Day of the month
WNUM=`date +%V`       #Week number
MONTH=`date +%B`      #Month name ie. May

#Log files
LOGFILE="/tmp/${DATE}_${BACKUPNAME}_backup.log"
LOGERR="/tmp/${DATE}_${BACKUPNAME}_errors.log"
TMPFILE="/tmp/${DATE}_${BACKUPNAME}.tmp"
DBGFILE="/tmp/${DATE}_${BACKUPNAME}_debug.log"

touch $LOGFILE
touch $LOGERR
touch $TMPFILE
touch $DBGFILE

#IO Redirection for logging

exec 6>&1           # Link file descriptor #6 with stdout.
exec > $LOGFILE     # stdout replaced with file $LOGFILE.

exec 7>&2           # Link file descriptor #7 with stderr.
exec 2> $LOGERR     # stderr replaced with file $LOGERR.

#Convenience function to facilitate writing debug messages
# Flags:
# n - Don't add a new line after the debug statement 
# d - Don't add the timestemp before the debug statement
debugMsg() {
    DBGSTAMP=`date +%H:%M:%S`
    
    if [ "$2" = "n" ]; then
	echo -n "[${DBGSTAMP}] $1" >> $DBGFILE
    elif [ "$2" = "d" ]; then
	echo $1 >> $DBGFILE
    else
	echo "[${DBGSTAMP}] $1" >> $DBGFILE
    fi
}

debugMsg "Moving to directory $TOBACKUP to start backups"
cd "$TOBACKUP"

debugMsg "Initializing backup directories.."

# Check for the base backup directory and create subdirs as needed
debugMsg "Checking for backup storage directory"
if [ ! -d "$BACKUPDIR" ]
    then
    debugMsg "Didn't find $BACKUPDIR directory, creating it: " "n"
    if mkdir -p "$BACKUPDIR" ; then
	debugMsg "Success!" "d"
    else
	debugMsg "FAILED!" "d"
    fi
fi

#Check if weeklies are enabled and if the backup directory exists
if [ $WEEKLY = "yes" ] && [ ! -d "${BACKUPDIR}/weekly"  ]
    then
    debugMsg "Weekly backups are enabled and ${BACKUPDIR}/weekly doesn't exist, creating it: " "n"
    mkdir -p "${BACKUPDIR}/weekly"
    debugMsg "Success!" "d"
fi

#Check if monthlies are enabled and if the backup directory exists
if [ $MONTHLY = "yes" ] && [ ! -d "${BACKUPDIR}/monthly"  ]
    then
    debugMsg "Monthly backups are enabled and ${BACKUPDIR}/monthly doesn't exist, creating it: " "n"
    mkdir -p "${BACKUPDIR}/monthly"
    debugMsg "Success!" "d"
fi

if [ $DBBACKUP = 'yes' ]
    then
    debugMsg "Database backups enabled, creating ${TOBACKUP}/${DBDUMPDIR}: " "n"
    mkdir -p "$DBDUMPDIR"
    debugMsg "Success!" "d"
fi


## Dump the database(s) if enabled
if [ $DBBACKUP = 'yes'  ]
    then

    for DB in $DBNAMES
    do
	debugMsg "Dumping database: $DB"
	DUMP="${DBDUMPDIR}/${DB}-${DATE}.sql"
	mysqldump --user=$DBUSERNAME --password=$DBPASSWORD $DB > $DUMP
    done
fi


#Backup function
## $1 - Where to put the backup file
## $2 - What is going to be backed up
## $3 - Formatted parameters to be passed to the cmd
## $4 - Additional debug message
backup() {
    echo "Backing up: $TOBACKUP"
    CMD="tar -cjf ${BACKUPDIR}/${1} $EXCLUDESTRING $2"
    eval $CMD
    
    debugMsg "$CMD"
}


for EXCLUDE in $EXCLUDEDIRS
do
    EXCLUDESTRING="${EXCLUDESTRING} --exclude \"${EXCLUDE}\""
    DUEXCLUDESTRING="${DUEXCLUDESTRING} --exclude=${EXCLUDE}"
    debugMsg "Excluding: $EXCLUDE"
done

debugMsg "Exclude string: $EXCLUDESTRING"


### Begin the backup job ###
echo "===== Backup Job $BACKUPNAME Log  ====="

## Weekly backup
if [ $WEEKLY = "yes"  ] && [ $DNOW = $WEEKLYDAY ]
then
    echo
    echo "----- Performing Weekly Backup -----"
    echo
    if [ $WNUM -le $WEEKLYROLL ]; then
	    REMW=`expr 48 + $WNUM`
    elif [ "$WNUM" -lt 15 ];then
        REMW=0`expr $WNUM - 5`
    else
        REMW=`expr $WNUM - $WEEKLYROLL`
    fi

    echo "Rotating weekly backup..."
    CMDP="rm -vf ${BACKUPDIR}/weekly/${BACKUPNAME}_*-${REMW}.tbz"
    
    debugMsg "$CMDP"
    
    eval $CMDP
    echo

    backup "weekly/${BACKUPNAME}_${DATE}_Week-${WNUM}.tbz" "." 
    BACKUPSIZE=`du -h ${BACKUPDIR}/weekly/${BACKUPNAME}_${DATE}_Week-${WNUM}.tbz | awk '{print $1}'`
    REALBACKUPDIR="$BACKUPDIR/weekly"
    
else
## Daily backup
    echo
    echo "----- Performing Daily Backup -----"
    echo
    echo "Rotating daily backups..."
    
    #Cap DAILYROLLWEEKS at 4, for now..
    if [ $DAILYROLLWEEKS -gt 4 ]; then
        DAILYROLLWEEKS=4
        debugMsg "Setting DAILYROLLWEEKS to a sane number: $DAILYROLLWEEKS"
    fi
    
    if [ $WNUM -le $DAILYROLLWEEKS ]; then 
	    RMW=`expr 48 + $WNUM`
    elif [ "$WNUM" -lt 15 ];then
        RMW=0`expr $WNUM - 5`
    else
        RMW=`expr $WNUM - $DAILYROLLWEEKS`
    fi
    CMDP="rm -vf ${BACKUPDIR}/${BACKUPNAME}_*_${RMW}-${DOW}.tbz"
    debugMsg "$CMDP"
    eval $CMDP

    backup "${BACKUPNAME}_${DATE}_${WNUM}-${DOW}.tbz" "."
    BACKUPSIZE=`du -h ${BACKUPDIR}/${BACKUPNAME}_${DATE}_${WNUM}-${DOW}.tbz | awk '{print $1}'`
    REALBACKUPDIR="$BACKUPDIR"
fi

UNCOMPBACKUPDIR=`du -sh $DUEXCLUDESTRING $TOBACKUP | awk '{print $1}'`
echo "Backed up $UNCOMPBACKUPDIR ($BACKUPSIZE compressed) to $REALBACKUPDIR"


## Monthly backup
if [ $MONTHLY = "yes"  ] && [ $DOM = $MONTHLYDAY ]
then 
    if [ $MONTHLYPURGE = "yes"  ]
	then
    	CMDP="rm -vf ${BACKUPDIR}/${BACKUPNAME}/monthly/${BACKUPNAME}_*_${MONTH}.tbz"
    	echo
    	echo "Rotating monthly backup..."
    	eval $CMDP
    fi
    
    CMD="tar -cjf ${BACKUPDIR}/${BACKUPNAME}/monthly/${BACKUPNAME}_${DATE}_${MONTH}.tbz"

    echo 
    echo "----- Performing Monthly Backup -----"
    echo
    backup "monthly/${BACKUPNAME}_${DATE}_${MONTH}.tbz" "."

    MBACKUPSIZE=`du -h ${BACKUPDIR}/weekly/${BACKUPNAME}_${DATE}_${MONTH}.tbz | awk '{print $1}'`
    MUNCOMPBACKUPDIR=`du -sh $DUEXCLUDESTRING $TOBACKUP | awk '{print $1}'`
    echo "Backed up $MUNCOMPBACKUPDIR ($MBACKUPSIZE compressed) to $BACKUPDIR/monthly"
fi

echo
echo "==============================================="
echo "Total disk space used for backup storage.."
echo
echo `du -sh $DUEXCLUDESTRING $BACKUPDIR | awk '{print $1 "  - " $2}'` 
echo "==============================================="

finish_time=$(date +%s)
debugMsg "Script execution time: $((finish_time - start_time)) secs."
 
# Email the output and any errors to the address specified
cat $LOGFILE > $TMPFILE
echo >> $TMPFILE
 
if [ -s "$LOGERR" ]
then
    echo "===== Errors Encountered =====" >> $TMPFILE
    cat $LOGERR >> $TMPFILE
    echo >> $TMPFILE
fi

if [ "$DEBUG" = "yes" ]
then
    echo "===== Debugging output =====" >> $TMPFILE
    cat $DBGFILE >> $TMPFILE
fi
 
#cat $TMPFILE | mail -s "Backup log from $BACKUPNAME $DATE backup" $EMAIL


#Clean up the DB Dumps
eval rm -rf $DBDUMPDIR

 
#Clean up IO redirection
exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.
exec 1>&7 7>&-      # Restore stdout and close file descriptor #7.


#For console debugging, uncomment this line
#Optionally comment out the mail line during debugging too, unless you like alot of email
cat $TMPFILE


# Clean up Logfile
eval rm -f "$LOGFILE"
eval rm -f "$LOGERR"
eval rm -f "$TMPFILE"
eval rm -f "$DBGFILE"